﻿// Copyright (c) .NET Foundation and Contributors. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;

namespace Roslynator.CSharp.Refactorings.ReplacePropertyWithMethod;

internal static class ReplacePropertyWithMethodRefactoring
{
    private static readonly string[] _prefixes = [
        "Is",
        "Has",
        "Are",
        "Can",
        "Allow",
        "Supports",
        "Should",
        "Get",
        "Set",
    ];

    public static void ComputeRefactoring(RefactoringContext context, PropertyDeclarationSyntax propertyDeclaration)
    {
        if (CanRefactor(context, propertyDeclaration))
        {
            context.RegisterRefactoring(
                $"Replace '{propertyDeclaration.Identifier.ValueText}' with method",
                ct => RefactorAsync(context.Document, propertyDeclaration, ct),
                RefactoringDescriptors.ReplacePropertyWithMethod);
        }
    }

    public static bool CanRefactor(RefactoringContext context, PropertyDeclarationSyntax propertyDeclaration)
    {
        AccessorDeclarationSyntax accessor = propertyDeclaration.AccessorList?.Accessors.SingleOrDefault(shouldThrow: false);

        if (accessor?.Kind() != SyntaxKind.GetAccessorDeclaration)
            return false;

        if (accessor.BodyOrExpressionBody() is not null)
            return true;

        return context.SupportsCSharp6
            && accessor.IsAutoImplemented()
            && propertyDeclaration.Initializer?.Value is not null;
    }

    public static async Task<Solution> RefactorAsync(
        Document document,
        PropertyDeclarationSyntax property,
        CancellationToken cancellationToken = default)
    {
        Solution solution = document.Solution();

        SemanticModel semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);

        IPropertySymbol propertySymbol = semanticModel.GetDeclaredSymbol(property, cancellationToken);

        string methodName = GetMethodName(property);

        ImmutableArray<DocumentReferenceInfo> infos = await SyntaxFinder.FindReferencesByDocumentAsync(propertySymbol, solution, allowCandidate: false, cancellationToken: cancellationToken).ConfigureAwait(false);

        foreach (DocumentReferenceInfo info in infos)
        {
            var rewriter = new ReplacePropertyWithMethodSyntaxRewriter(info.References, methodName, property);

            SyntaxNode newRoot = rewriter.Visit(info.Root);

            solution = solution.WithDocumentSyntaxRoot(info.Document.Id, newRoot);
        }

        if (!infos.Any(f => f.Document.Id == document.Id))
        {
            SyntaxNode root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

            SyntaxNode newRoot = root.ReplaceNode(property, ToMethodDeclaration(property));

            solution = solution.WithDocumentSyntaxRoot(document.Id, newRoot);
        }

        return solution;
    }

    public static MethodDeclarationSyntax ToMethodDeclaration(PropertyDeclarationSyntax property)
    {
        AccessorDeclarationSyntax getter = property.Getter();

        BlockSyntax getterBody = getter.Body;

        BlockSyntax methodBody;

        if (getterBody is not null)
        {
            methodBody = Block(getterBody.Statements);
        }
        else
        {
            ArrowExpressionClauseSyntax getterExpressionBody = getter.ExpressionBody;

            if (getterExpressionBody is not null)
            {
                methodBody = Block(ReturnStatement(getterExpressionBody.Expression));
            }
            else
            {
                methodBody = Block(ReturnStatement(property.Initializer.Value));
            }
        }

        methodBody = methodBody.WithTrailingTrivia(property.GetTrailingTrivia());

        MethodDeclarationSyntax method = MethodDeclaration(
            property.AttributeLists,
            property.Modifiers,
            property.Type,
            property.ExplicitInterfaceSpecifier,
            Identifier(GetMethodName(property)).WithLeadingTrivia(property.Identifier.LeadingTrivia),
            default(TypeParameterListSyntax),
            ParameterList().WithTrailingTrivia(property.Identifier.TrailingTrivia),
            default(SyntaxList<TypeParameterConstraintClauseSyntax>),
            methodBody,
            default(ArrowExpressionClauseSyntax));

        return method
            .WithLeadingTrivia(property.GetLeadingTrivia())
            .WithFormatterAnnotation();
    }

    public static string GetMethodName(PropertyDeclarationSyntax propertyDeclaration)
    {
        string methodName = propertyDeclaration.Identifier.ValueText;

        if (!_prefixes.Any(prefix => StringUtility.HasPrefix(methodName, prefix)))
            methodName = "Get" + methodName;

        return methodName;
    }
}
